﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using MoroMoro.Samples.ToDoList.Base.Domain.ExpressionVisitors;

namespace MoroMoro.Samples.ToDoList.Base.Domain.Expanders
{
    /// <summary>
    /// 式ツリー内の特定のプロパティを適切な式ツリーに展開するためのエキスパンダを表します。
    /// </summary>
    /// <typeparam name="TEntity">エンティティの型。</typeparam>
    /// <seealso cref="IExpandable&lt;TEntity&gt;"/>
    /// <seealso cref="Specifications.Specification&lt;TEntity&gt;"/>
    /// <seealso cref="Viewpoints.Viewpoint&lt;TEntity, TValue&gt;"/>
    public sealed class Expander<TEntity>
        where TEntity : class
    {
        /// <summary>
        /// <see cref="Expander&lt;TEntity&gt;"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="expandables">展開可能な仕様や視点の一覧。</param>
        public Expander(params IExpandable<TEntity>[] expandables)
        {
            _expandables = expandables ?? new IExpandable<TEntity>[0];
        }


        /// <summary>
        /// 展開可能な仕様や視点の一覧。
        /// </summary>
        private readonly IEnumerable<IExpandable<TEntity>> _expandables;


        /// <summary>
        /// 指定した式ツリー内の特定のプロパティを適切な式ツリーに展開します。
        /// </summary>
        /// <typeparam name="T">式ツリーの戻り値の型。</typeparam>
        /// <param name="source">式ツリー。</param>
        /// <returns>展開された式ツリー。</returns>
        /// <exception cref="System.ArgumentNullException">source が null です。</exception>
        internal Expression<Func<TEntity, T>> Expand<T>(Expression<Func<TEntity, T>> source)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            var adjuster = new ParameterVisitor(node => source.Parameters.Single());
            var adjustedExpandables = 
                from expandable in _expandables
                select new
                {
                    From = expandable.From(),
                    To = (adjuster.Visit(expandable.To()))
                };

            var propertyReplacer = new MemberVisitor(node =>
            {
                var query = 
                    from expandable in adjustedExpandables
                    where (expandable.From == node.Member)
                    select expandable.To;
                return query.SingleOrDefault() ?? node;
            });

            return (Expression<Func<TEntity, T>>)propertyReplacer.Visit(source);
        }
    }
}
